/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cse308;

import create_view_modify.UserBean;
import cse308.entities.Courseinfo;
import cse308.entities.Courseoffering;
import cse308.entities.Degreeprogram;
import cse308.entities.Minute;
import cse308.entities.Survey;
import cse308.entities.Users;
import cse308.helper_classes.CourseofferingClass;
import cse308.helper_classes.DegreeProgramClass;
import cse308.helper_classes.GeneralTest;
import cse308.helper_classes.MinuteClass;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;

import java.util.logging.Logger;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Id;
import javax.persistence.Persistence;
import javax.persistence.Query;
import javax.servlet.http.Part;

/**
 *
 * @author Aaron
 */
@ManagedBean(name = "gUIUtilityClass")
@SessionScoped
public class GUIUtilityClass implements Serializable {
    //general identifier

    @ManagedProperty(value = "#{userSessionBean}")
    private static UserSessionBean userSessionBean;
    public static int minuteIterator = 0;
    public static int courseOfferingIterator = 0;
    public static String createWarning="Already exists in database";
    public static String duplicateWarning="There are duplicate entries";
    private final static Logger LOG = Logger.getLogger(GUIUtilityClass.class.getName());
    private List<GeneralEntity> currentRow = new ArrayList();
    public UserSessionBean getUserSessionBean() {
        return userSessionBean;
    }

    public void setUserSessionBean(UserSessionBean userSessionBean) {
        this.userSessionBean = userSessionBean;
    }

    public static int getMinuteIterator() {
        return minuteIterator;
    }

    public static void setMinuteIterator(int minuteIterator) {
        GUIUtilityClass.minuteIterator = minuteIterator;
    }

    public static int getCourseOfferingIterator() {
        return courseOfferingIterator;
    }

    public static void setCourseOfferingIterator(int courseOfferingIterator) {
        GUIUtilityClass.courseOfferingIterator = courseOfferingIterator;
    }

    public GUIUtilityClass() {
        LOG.info("create gui");
    }

    public String viewOptions() {
        return "ViewModifyOptions";
    }

    public String modifyOptions() {
        return "ModifyOptions";
    }

    public String createOptions() {
        return "CreateOptions";
    }

    public String createCourses() {
        return "CreateCoursePage";
    }

    public String createDegreePrograms() {
        return "CreateDegreeProgramsPage";
    }

    public String createMinute() {

        return "CreateMinutePage";
    }

    public String createPeos() {
        return "CreatePeosPage";
    }

    public String createCourseOffering() {
        return "CreateCourseOffering";
    }

    public List<GeneralTest> generalizeModify(String entity, String entityClass,
            List<String> associatedClasses, List<String> associatedEntities,
            List<String> setMethods, List<String> setMethods2) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        this.currentRow = new ArrayList();
        List<GeneralTest> generalClasses = new ArrayList();
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("CSE_308_ABETPU");
        EntityManager em = emf.createEntityManager();

        List<GeneralEntity> results = em.createNamedQuery(Class.forName(entity).getSimpleName() + ".findAll").getResultList();

        for (GeneralEntity i : results) {
            Class c = Class.forName(entityClass);
            Class e = Class.forName(entity);
            Constructor constr = c.getConstructor(e);

            GeneralTest currentUser = (GeneralTest) constr.newInstance(i);
            this.genModify(associatedEntities, associatedClasses, currentUser, setMethods, setMethods2, em);
            if (isUserRole(getPath(i), 0, i)) {
                generalClasses.add(currentUser);
                this.currentRow.add(i);
            }

        }
        return generalClasses;
    }

    private List<String> getPath(GeneralEntity ge) {
        /*
         * course, courseoffering, survey, minute
         */
        List<String> path = new ArrayList();

        if (ge instanceof Courseoffering) {
            path.add(Courseinfo.class.getSimpleName());
            path.add(Degreeprogram.class.getSimpleName());
            path.add(Users.class.getSimpleName());
        } else if (ge instanceof Survey || ge instanceof Degreeprogram || ge instanceof Courseinfo || ge instanceof Minute) {
            path.add(Degreeprogram.class.getSimpleName());
            path.add(Users.class.getSimpleName());
        } else if (ge instanceof Degreeprogram) {
            path.add(Users.class.getSimpleName());
        }
        return path;
    }

    private void genModify(List<String> entityType, List<String> addedClass, GeneralTest i,
            List<String> set1, List<String> set2, EntityManager em) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InstantiationException {
        ArrayList<Map<String, General>> maps = i.getMap();
        for (int k = 0; k < entityType.size(); k++) {
            System.out.println(entityType.get(k));
            String simpleName = Class.forName(entityType.get(k)).getSimpleName();
            List<GeneralEntity> currentResults = em.createNamedQuery(simpleName + ".findAll").getResultList();//results2.get(k);

            Map<String, General> currentMap = maps.get(k);

            List<GeneralEntity> ownObjects = i.findCorrespondingEntity().getAssociatedClass(simpleName);// queryCreated.getResultList();

            List<General> allObjects = new ArrayList();
            List<General> ownObject = new ArrayList();

            for (General o : currentResults) {
                Class c = Class.forName(addedClass.get(k));
                Class entity = Class.forName(entityType.get(k));
                General newGClass;
                Constructor constru = (this.hasGeneralEntity(c)) ? c.getConstructor() : c.getConstructor(entity);
                if (constru.getParameterTypes().length == 0) {
                    newGClass = o;
                } else {
                    newGClass = (General) constru.newInstance(o);
                }
                allObjects.add(newGClass);
                for (Object o2 : ownObjects) {
                    if (o2.equals(o)) {
                        ownObject.add(newGClass);
                    }
                }
            }

            List<String> ownDegreeProgramsString = new ArrayList();
            List<String> degreeProgramssString = new ArrayList();
            for (General gt : ownObject) {
                ownDegreeProgramsString.add(gt.getIdentifier2());
            }
            for (General gt : allObjects) {
                degreeProgramssString.add(gt.getIdentifier2());
                currentMap.put(gt.getIdentifier2(), gt);
            }
            i.getClass().getMethod(set1.get(k), List.class).invoke(i, ownDegreeProgramsString);
            i.getClass().getMethod(set2.get(k), List.class).invoke(i, degreeProgramssString);
        }
    }

    public boolean hasGeneralEntity(Class c) {
        Class[] interfaces = c.getInterfaces();
        for (int i = 0; i < interfaces.length; i++) {
            if (interfaces[i].getCanonicalName().equals(GeneralEntity.class.getCanonicalName())) {
                return true;
            }
        }
        return false;
    }

    public boolean isInBothLists(List<String> list1, List<String> list2) {
        for (int j = 0; j < list1.size(); j++) {
            for (int k = 0; k < list2.size(); k++) {
                if (list1.get(j).equals(list2.get(k))) {
                    return true;
                }
            }
        }
        return false;
    }

    public boolean generalizedSave(String entityName, List<GeneralTest> rowsClasses, List<String> innerQueries)
            throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException,
            NoSuchMethodException, InvocationTargetException, ClassNotFoundException, InstantiationException {
        if(this.checkDuplicates(rowsClasses)){
           return false;
        }
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("CSE_308_ABETPU");
        EntityManager em = emf.createEntityManager();
        em.getTransaction().begin();

        List<GeneralEntity> results = this.currentRow;

        ArrayList<AssociateRemoveOrAdd> removeList = new ArrayList();
        ArrayList<AssociateRemoveOrAdd> addList = new ArrayList();


        for (int i = 0; i < rowsClasses.size(); i++) {
            GeneralTest u1 = rowsClasses.get(i);
            GeneralEntity u2 =  results.get(i);
            Field[] fieldsU1 = u1.getClass().getDeclaredFields();
            Field[] fieldsU2 = u2.getClass().getDeclaredFields();

            for (Field f : fieldsU2) {
                String fieldName = f.getName();
                Id checkIfPrimary=f.getAnnotation(Id.class);
                LOG.info("current field: "+fieldName);
                if(checkIfPrimary !=null){
                    LOG.info("found primary key, skipping");
                    continue;
                }
                for (Field f2 : fieldsU1) {
                    String fieldName2 = f2.getName();
                    if (fieldName.equals(fieldName2)) {
                        String firstLetterU2 = f.getName().substring(0, 1).toUpperCase();
                        String setFunction = "set" + firstLetterU2 + f.getName().substring(1);
                        String getFunction = "get" + firstLetterU2 + f.getName().substring(1);
                        Method getM = u1.getClass().getMethod(getFunction);
                        Method setM = u2.getClass().getMethod(setFunction, f2.getType());
                        setM.invoke(u2, getM.invoke(u1));
                        break;
                    }
                }
            }

            //list of other tables
            ArrayList<List<String>> ownObjects = u1.getList();
            ArrayList<Map<String, General>> ownMaps = u1.getMap();
            for (int j = 0; j < ownObjects.size(); j++) {
                List<String> ownObject2 = ownObjects.get(j);
                Map<String, General> opc = ownMaps.get(j);
                List<General> ownObject = new ArrayList();
                for (int k = 0; k < ownObject2.size(); k++) {
                    ownObject.add(opc.get(ownObject2.get(k)));
                }

                List<GeneralEntity> degreeProgram = (List) u1.findCorrespondingEntity().getAssociatedClass(innerQueries.get(j));

                //create copies
                List<GeneralEntity> degreeProgram2 = new ArrayList();
                degreeProgram2.addAll(degreeProgram);

                List<General> ownDegreePrograms3 = new ArrayList();
                ownDegreePrograms3.addAll(ownObject);

                for (int k = 0; k < ownObject.size(); k++) {
                    for (int ii = 0; ii < degreeProgram.size(); ii++) {
                        if (ownObject.get(k).equalsEntity(degreeProgram.get(ii))) {
                            degreeProgram2.remove(degreeProgram.get(ii));
                            ownDegreePrograms3.remove(ownObject.get(k));
                            break;
                        }
                    }
                }

                for (int k = 0; k < degreeProgram2.size(); k++) {
                    GeneralEntity dp = degreeProgram2.get(k);
                    u2.removeClass(dp);
                    removeList.add(new AssociateRemoveOrAdd(dp, u2));

                }
                for (int k = 0; k < ownDegreePrograms3.size(); k++) {
                    GeneralEntity dp = ownDegreePrograms3.get(k).findCorrespondingEntity();
                    u2.testAddClass(dp);
                    addList.add(new AssociateRemoveOrAdd(dp, u2));
                }
                em.merge(u2);
            }
            em.merge(u2);
        }
        /*guarentees that in a one to many relationship if an entity is removed from one other entity
         and added to another that it is first removed then added.*/
        this.addRemoveGuarenteerer(removeList, addList, em);
        em.getTransaction().commit();
        return true;
    }
    public void addRemoveGuarenteerer(List<AssociateRemoveOrAdd> removeList, List<AssociateRemoveOrAdd> addList,
            EntityManager em){
         for (int j = 0; j < removeList.size(); j++) {
            AssociateRemoveOrAdd remove = removeList.get(j);
            remove.makeChange(false, em);
        }
        for (int j = 0; j < addList.size(); j++) {
            AssociateRemoveOrAdd add = addList.get(j);
            add.makeChange(true, em);
        }
    }
    public static Collection<GeneralEntity> convertToGE(Collection c) {
        Collection<GeneralEntity> g = new ArrayList();
        for (Object c2 : c) {
            g.add((GeneralEntity) c2);
        }
        return g;
    }

    public boolean checkOneToMany(List<GeneralTest> gt, int manyToMany) {
        for (int j = 0; j < gt.size() - 1; j++) {
            for (int k = j + 1; k < gt.size(); k++) {
                ArrayList<List<String>> gt1 = gt.get(j).getList();
                ArrayList<List<String>> gt2 = gt.get(k).getList();
                for (int ii = 0; ii < gt1.size(); ii++) {
                    if (ii == manyToMany) {
                        continue;
                    }
                    List<String> theList = gt1.get(ii);
                    List<String> otherList = gt2.get(ii);
                    if (isInBothLists(theList, otherList)) {
                        LOG.info("relationship violation");
                        return true;
                    }
                }
            }
        }
        return false;
    }
    public boolean checkDuplicates(List<GeneralTest> gtl){
        for(int i=0; i<gtl.size()-1; i++){
            for(int j=i+1; j<gtl.size(); j++){
                Object pk1 = gtl.get(i).findCorrespondingEntity().getPK();
                Object pk2 = gtl.get(j).findCorrespondingEntity().getPK();
                if(pk1.equals(pk2)){
                    return true;
                }
            }
        }
        return false;
    }
    public boolean checkPK(GeneralEntity ge) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("CSE_308_ABETPU");
        EntityManager em = emf.createEntityManager();
        GeneralEntity inDataBase = em.find(ge.getClass() ,ge.getPK());
        if(inDataBase==null)return false;
        return true;
    }

    public static File convertToFile(byte[] bytes, String name) throws FileNotFoundException, IOException {
        if (bytes == null) {
            return null;
        }
        File file = new File(name);
        FileOutputStream fos = new FileOutputStream(name);
        fos.write(bytes);
        fos.close();
        return file;
    }

    public static byte[] convertToBytesByPart(Part file) {
        if (file == null) {
            return null;
        }
        byte[] bytes = new byte[(int) file.getSize()];
        try {
            InputStream is = file.getInputStream();
            //if(is.available()>0){
            is.read(bytes);
            //}
        } catch (IOException ex) {
            Logger.getLogger(CourseofferingClass.class.getName()).log(Level.SEVERE, null, ex);
        }
        return bytes;
    }

    public static byte[] convertToBytes(File file) {
        if (file == null) {
            return null;
        }
        byte[] bytes = new byte[(int) file.getTotalSpace()];
        try {
            FileInputStream is = new FileInputStream(file);
            is.read(bytes);


        } catch (IOException ex) {
            Logger.getLogger(CourseofferingClass.class
                    .getName()).log(Level.SEVERE, null, ex);
        }
        return bytes;
    }
    /*
     * if co->c->dp->user
     * start path at dp
     * start result list at c
     */

    public static boolean isUserRole(List<String> path, int currentLocationOnPath, GeneralEntity result) {
        if (currentLocationOnPath < path.size()) {
            List<GeneralEntity> results = result.getAssociatedClass(path.get(currentLocationOnPath));
            for (GeneralEntity ge : results) {
                int newLocation = currentLocationOnPath + 1;
                boolean found = isUserRole(path, newLocation, ge);
                if (found) {
                    return true;
                }
            }
        } else {
            if (!(result instanceof Users)) {
                return false;
            } else {
                if (currentLocationOnPath == 0) {
                    return true;
                } else {
                    Users currentUser = (Users) result;
                    if (currentUser.getUsername().equalsIgnoreCase(userSessionBean.getUsername())) {
                        LOG.info("Has Degree Programs in db");
                        return true;
                    }
                }
            }
        }
        return false;
    }

    public String tabulateNow() {
        return "TabulateOptions";
    }
}